# Copyright (c) 2017 Presto Labs Pte. Ltd.
# Author: inkyu

import ujson as json
import functools

from coin.base.book.types import TradeSide, create_market_trade_event
from coin.exchange.base.ws.handler import WebsocketHandler
from coin.exchange.gdax.kr_rest.product import GdaxProduct
from coin.exchange.gdax.book_builder import BookBuilder


@functools.lru_cache(maxsize=64)
def get_product_from_product_id(product_id):
  return GdaxProduct.FromStrNativeProduct(product_id)


class GdaxHandler(WebsocketHandler):
  def on_book(self, record, queue_data, topic_data):
    message = record.data
    if not isinstance(message, dict):
      message = json.loads(message.tobytes())
    message_type = message['type']
    if message_type == 'snapshot':
      product_id = message['product_id']
      product = get_product_from_product_id(product_id)

      if product_id not in self._books:
        book = BookBuilder(product_id, book_builder_observer=self._book_builder_observer)
        self._books[product_id] = book
      else:
        book = self._books[product_id]
        book.clear_book()

      bids = message['bids']
      asks = message['asks']
      book.snapshot(bids, asks)
      book.update_timestamp(record.timestamp)
      book.update_topic_id(record.topic_id)
      self.ready = True

    elif message_type == 'l2update':
      product_id = message['product_id']
      product = get_product_from_product_id(product_id)
      if not self.ready or product_id not in self._books:
        return []
      # time = message['time']  # TODO: Time check
      changes = message['changes']
      book = self._books[product_id]
      book.update(changes)
      book.update_timestamp(record.timestamp)
      book.update_topic_id(record.topic_id)

      if book.has_bid() and book.has_ask():
        assert book.bid0().price < book.ask0().price

    else:
      raise ValueError('Unknown type: %s' % message_type)

    if self._feed_checker:
      self._feed_checker.on_book(product=product, book=book, timestamp=record.timestamp)
    self.publish_event('book', product_id, book)

  def on_full(self, record, queue_data, topic_data):
    message = record.data
    if not isinstance(message, dict):
      message = json.loads(message.tobytes())
    event = message['type']
    # Only process match event.
    if event == 'match':
      self.on_full_match(record, queue_data, topic_data)

  def on_full_match(self, record, queue_data, topic_data):
    message = record.data
    if not isinstance(message, dict):
      message = json.loads(message.tobytes())

    event = message['type']
    assert event == 'match'
    symbol = message['product_id']
    product = get_product_from_product_id(symbol)
    if not self.is_subscribed('trade', symbol):
      return
    price = float(message['price'])
    qty = float(message['size'])
    side = message['side']
    assert side == 'buy' or side == 'sell'
    # The side in Gdax indicates maker order side.
    side = TradeSide.TRADE_SELL_SIDE if side == 'buy' else TradeSide.TRADE_BUY_SIDE
    trade_event = create_market_trade_event(record.timestamp,
                                            symbol,
                                            price,
                                            qty,
                                            side,
                                            topic_id=record.topic_id)
    if self._feed_checker:
      self._feed_checker.on_trade(product=product,
                                  trade_event=trade_event,
                                  timestamp=record.timestamp)
    self.publish_event('trade', symbol, trade_event)
